//----------------------------------------------
//            MeshBaker
// Copyright © 2011-2012 Ian Deane
//----------------------------------------------
using UnityEngine;
using System.Collections.Generic;

namespace DigitalOpus.MB.Core
{
    /// <summary>
    /// This class is an endless mesh. You don't need to worry about the 65k limit when adding meshes. It is like a List of combined meshes. Internally it manages
    /// a collection of MB2_MeshComber objects to which meshes added and deleted as necessary. 
    /// 
    /// Note that this implementation does
    /// not attempt to split meshes. Each mesh is added to one of the internal meshes as an atomic unit.
    /// 
    /// This class is not a Component so it can be instantiated and used like a regular C Sharp class.
    /// </summary>
    [System.Serializable]
    public class MB3_MultiMeshCombiner : MB3_MeshCombiner
    {
        [System.Serializable]
        public class CombinedMesh
        {
            public MB3_MeshCombinerSingle combinedMesh;
            public int extraSpace = -1;
            public int numVertsInListToDelete = 0;
            public int numVertsInListToAdd = 0;
            public List<GameObject> gosToAdd;
            public List<int> gosToDelete;
            public List<GameObject> gosToUpdate;
            public bool isDirty = false; //needs apply

            public CombinedMesh(int maxNumVertsInMesh, GameObject resultSceneObject, MB2_LogLevel ll)
            {
                combinedMesh = new MB3_MeshCombinerSingle();
                combinedMesh.resultSceneObject = resultSceneObject;
                combinedMesh.LOG_LEVEL = ll;
                extraSpace = maxNumVertsInMesh;
                numVertsInListToDelete = 0;
                numVertsInListToAdd = 0;
                gosToAdd = new List<GameObject>();
                gosToDelete = new List<int>();
                gosToUpdate = new List<GameObject>();
            }

            public bool isEmpty()
            {
                List<GameObject> obsIn = new List<GameObject>();
                obsIn.AddRange(combinedMesh.GetObjectsInCombined());
                for (int i = 0; i < gosToDelete.Count; i++)
                {
                    for (int j = 0; j < obsIn.Count; j++)
                    {
                        if (obsIn[j].GetInstanceID() == gosToDelete[i])
                        {
                            obsIn.RemoveAt(j);
                            break;
                        }
                    }

                }
                if (obsIn.Count == 0) return true;
                return false;
            }
        }

        static GameObject[] empty = new GameObject[0];
        static int[] emptyIDs = new int[0];

        public override MB2_LogLevel LOG_LEVEL
        {
            get { return _LOG_LEVEL; }
            set
            {
                _LOG_LEVEL = value;
                for (int i = 0; i < meshCombiners.Count; i++)
                {
                    meshCombiners[i].combinedMesh.LOG_LEVEL = value;
                }
            }
        }

        public override MB2_ValidationLevel validationLevel
        {
            set
            {
                _validationLevel = value;
                for (int i = 0; i < meshCombiners.Count; i++)
                {
                    meshCombiners[i].combinedMesh.validationLevel = _validationLevel;
                }
            }
            get { return _validationLevel; }
        }

        public Dictionary<int, CombinedMesh> obj2MeshCombinerMap = new Dictionary<int, CombinedMesh>();
        [SerializeField]
        public List<CombinedMesh> meshCombiners = new List<CombinedMesh>();

        [SerializeField]
        int _maxVertsInMesh = 65535;
        public int maxVertsInMesh
        {
            get { return _maxVertsInMesh; }
            set
            {
                if (obj2MeshCombinerMap.Count > 0)
                {
                    //todo how to warn with gui
                    //Debug.LogError("Can't set the max verts in meshes once there are objects in the mesh.");
                    return;
                }
                else if (value < 3)
                {
                    Debug.LogError("Max verts in mesh must be greater than three.");
                }
                else if (value > MBVersion.MaxMeshVertexCount())
                {
                    Debug.LogError("MultiMeshCombiner error in maxVertsInMesh. Meshes in unity cannot have more than " + MBVersion.MaxMeshVertexCount() + " vertices. " + value);
                }
                else
                {
                    _maxVertsInMesh = value;
                }
            }
        }

        public override int GetNumObjectsInCombined()
        {
            return obj2MeshCombinerMap.Count;
        }

        public override List<GameObject> GetObjectsInCombined()
        { //todo look at getting from keys
            List<GameObject> allObjs = new List<GameObject>();
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                allObjs.AddRange(meshCombiners[i].combinedMesh.GetObjectsInCombined());
            }
            return allObjs;
        }

        public override int GetLightmapIndex()
        { //todo check that all meshcombiners use same lightmap index
            if (meshCombiners.Count > 0) return meshCombiners[0].combinedMesh.GetLightmapIndex();
            return -1;
        }

        public override bool CombinedMeshContains(GameObject go)
        {
            return obj2MeshCombinerMap.ContainsKey(go.GetInstanceID());
        }

        bool _validateTextureBakeResults()
        {
            if (_textureBakeResults == null)
            {
                Debug.LogError("Texture Bake Results is null. Can't combine meshes.");
                return false;
            }
            if ((_textureBakeResults.materialsAndUVRects == null || _textureBakeResults.materialsAndUVRects.Length == 0))
            {
                Debug.LogError("Texture Bake Results has no materials in material to sourceUVRect map. Try baking materials. Can't combine meshes. " +
                     "If you are trying to combine meshes without combining materials, try removing the Texture Bake Result.");
                return false;
            }


            if (_textureBakeResults.NumResultMaterials() == 0)
            {
                Debug.LogError("Texture Bake Results has no result materials. Try baking materials. Can't combine meshes.");
                return false;
            }

            return true;
        }

        public override bool Apply(MB3_MeshCombiner.GenerateUV2Delegate uv2GenerationMethod)
        {
            bool success = true;
#if DEBUG
            int numInCombined = 0;
#endif
            if (_bakeStatus != MeshCombiningStatus.readyForApply)
            {
                Debug.LogError("Apply was called when combiner was not in 'readyForApply' state. Did you call AddDelete(), Update() or ShowHide()");
                return false;
            }

            for (int i = 0; i < meshCombiners.Count; i++)
            {
                if (meshCombiners[i].isDirty)
                {
                    success &= meshCombiners[i].combinedMesh.Apply(uv2GenerationMethod);
                    meshCombiners[i].isDirty = false;
                }
#if DEBUG
             if (!settings.clearBuffersAfterBake) numInCombined += meshCombiners[i].combinedMesh.GetNumObjectsInCombined();
#endif
            }

            if (settings.clearBuffersAfterBake)
            {
                // Don't do full ClearBuffers because meshCombiners[i].combinedMesh.Apply(..) did that
                obj2MeshCombinerMap.Clear();
            }
#if DEBUG
            Debug.Assert(obj2MeshCombinerMap.Count == numInCombined, "num mismatch: " +  obj2MeshCombinerMap.Count + "  " +  numInCombined);
#endif
            _bakeStatus = MeshCombiningStatus.preAddDeleteOrUpdate;
            return success;
        }

        public override bool Apply(bool triangles, bool vertices, bool normals, bool tangents, bool uvs, bool uv2, bool uv3, bool uv4, bool colors, bool bones = false, bool blendShapeFlag = false, GenerateUV2Delegate uv2GenerationMethod = null)
        {
            return Apply(triangles, vertices, normals, tangents,
                uvs, uv2, uv3, uv4,
                false, false, false, false,
                colors, bones, blendShapeFlag, uv2GenerationMethod);
        }

        public override bool Apply(bool triangles,
                          bool vertices,
                          bool normals,
                          bool tangents,
                          bool uvs,
                          bool uv2,
                          bool uv3,
                          bool uv4,
                          bool uv5,
                          bool uv6,
                          bool uv7,
                          bool uv8,
                          bool colors,
                          bool bones = false,
                          bool blendShapesFlag = false,
                          MB3_MeshCombiner.GenerateUV2Delegate uv2GenerationMethod = null)
        {
#if DEBUG
            int numInCombined = 0;
#endif
            if (_bakeStatus != MeshCombiningStatus.readyForApply)
            {
                Debug.LogError("Apply was called when combiner was not in 'readyForApply' state. Did you call AddDelete(), Update() or ShowHide()");
                return false;
            }

            bool success = true;

            for (int i = 0; i < meshCombiners.Count; i++)
            {
                if (meshCombiners[i].isDirty)
                {
                    success &= meshCombiners[i].combinedMesh.Apply(triangles, vertices, normals, tangents, uvs, uv2, uv3, uv4, colors, bones, blendShapesFlag, uv2GenerationMethod);
                    meshCombiners[i].isDirty = false;
                }

#if DEBUG
                numInCombined += meshCombiners[i].combinedMesh.GetNumObjectsInCombined();
#endif
            }

            if (settings.clearBuffersAfterBake)
            {
                // Don't do full ClearBuffers because meshCombiners[i].combinedMesh.Apply(..) did that
                obj2MeshCombinerMap.Clear();
            }
#if DEBUG
            Debug.Assert(numInCombined == obj2MeshCombinerMap.Count);
#endif

            _bakeStatus = MeshCombiningStatus.preAddDeleteOrUpdate;
            return success;
        }

        public override void UpdateSkinnedMeshApproximateBounds()
        {
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBounds();
            }
        }

        public override void UpdateSkinnedMeshApproximateBoundsFromBones()
        {
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBoundsFromBones();
            }
        }

        public override void UpdateSkinnedMeshApproximateBoundsFromBounds()
        {
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBoundsFromBounds();
            }
        }

        public override bool UpdateGameObjects(GameObject[] gos, bool recalcBounds,
                                        bool updateVertices, bool updateNormals, bool updateTangents,
                                        bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4,
                                        bool updateColors, bool updateSkinningInfo)
        {
            return UpdateGameObjects(gos, recalcBounds, updateVertices, updateNormals, updateTangents,
                updateUV, updateUV2, updateUV3, updateUV4, false, false, false, false,
                updateColors, updateSkinningInfo);
        }

        public override bool UpdateGameObjects(GameObject[] gos, bool recalcBounds,
                                        bool updateVertices, bool updateNormals, bool updateTangents,
                                        bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4,
                                        bool updateUV5, bool updateUV6, bool updateUV7, bool updateUV8,
                                        bool updateColors, bool updateSkinningInfo)
        {
            if (gos == null)
            {
                Debug.LogError("list of game objects cannot be null");
                return false;
            }

            if (_bakeStatus != MeshCombiningStatus.preAddDeleteOrUpdate)
            {
                Debug.LogError("Bake Status of combiner was not 'preAddDeleteOrUpdate'. This can happen if AddDeleteGameObjects or UpdateGameObjects is called" +
                    " twice without calling Apply. You can call 'ClearBuffers' to reset the combiner.");
                return false;
            }

            //build gos lists
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                meshCombiners[i].gosToUpdate.Clear();
            }

            for (int i = 0; i < gos.Length; i++)
            {
                CombinedMesh cm = null;
                obj2MeshCombinerMap.TryGetValue(gos[i].GetInstanceID(), out cm);
                if (cm != null)
                {
                    cm.gosToUpdate.Add(gos[i]);
                }
                else
                {
                    Debug.LogWarning("Object " + gos[i] + " is not in the combined mesh.");
                }
            }

            bool success = true;
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                if (meshCombiners[i].gosToUpdate.Count > 0)
                {
                    meshCombiners[i].isDirty = true;
                    GameObject[] gosToUpdate = meshCombiners[i].gosToUpdate.ToArray();
                    success = success && meshCombiners[i].combinedMesh.UpdateGameObjects(gosToUpdate, recalcBounds, updateVertices, updateNormals, updateTangents,
                        updateUV, updateUV2, updateUV3, updateUV4, updateUV5, updateUV6, updateUV7, updateUV8,
                        updateColors, updateSkinningInfo);
                }
            }

            _bakeStatus = MeshCombiningStatus.readyForApply;
            return success;
        }

        public override bool AddDeleteGameObjects(GameObject[] gos, GameObject[] deleteGOs, bool disableRendererInSource = true)
        {
            int[] delInstanceIDs = null;
            if (deleteGOs != null)
            {
                delInstanceIDs = new int[deleteGOs.Length];
                for (int i = 0; i < deleteGOs.Length; i++)
                {
                    if (deleteGOs[i] == null)
                    {
                        Debug.LogError("The " + i + "th object on the list of objects to delete is 'Null'");
                    }
                    else
                    {
                        delInstanceIDs[i] = deleteGOs[i].GetInstanceID();
                    }
                }
            }
            return AddDeleteGameObjectsByID(gos, delInstanceIDs, disableRendererInSource);
        }

        public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource = true)
        {
            //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1");
            //PART 1 ==== Validate

            if (_bakeStatus != MeshCombiningStatus.preAddDeleteOrUpdate)
            {
                Debug.LogError("Bake Status of combiner was not 'preAddDeleteOrUpdate'. This can happen if AddDeleteGameObjects or UpdateGameObjects is called" +
                    " twice without calling Apply. You can call 'ClearBuffers' to reset the combiner.");
                return false;
            }

            if (deleteGOinstanceIDs == null) deleteGOinstanceIDs = emptyIDs;
            if (_usingTemporaryTextureBakeResult && gos != null && gos.Length > 0)
            {
                MB_Utility.Destroy(_textureBakeResults);
                _textureBakeResults = null;
                _usingTemporaryTextureBakeResult = false;
            }

            //if all objects use the same material we can create a temporary _textureBakeResults 
            if (_textureBakeResults == null && gos != null && gos.Length > 0 && gos[0] != null)
            {
                if (!_CreateTemporaryTextrueBakeResult(gos, GetMaterialsOnTargetRenderer()))
                {
                    return false;
                }
            }

            if (!_validate(gos, deleteGOinstanceIDs))
            {
                return false;
            }
            _distributeAmongBakers(gos, deleteGOinstanceIDs);
            if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.AddDeleteGameObjects numCombinedMeshes: " + meshCombiners.Count
                + " added:" + (gos == null ? 0 : gos.Length) 
                + " deleted:" + (deleteGOinstanceIDs == null ? 0 : deleteGOinstanceIDs.Length) 
                + " disableRendererInSource:" + disableRendererInSource 
                + " maxVertsPerCombined:" + _maxVertsInMesh);

            bool result = _bakeStep1(gos, deleteGOinstanceIDs, disableRendererInSource);
            _bakeStatus = MeshCombiningStatus.readyForApply;
            return result; 
        }

        bool _validate(GameObject[] gos, int[] deleteGOinstanceIDs)
        {
            if (_validationLevel == MB2_ValidationLevel.none) return true;
            if (_maxVertsInMesh < 3) Debug.LogError("Invalid value for maxVertsInMesh=" + _maxVertsInMesh);
            _validateTextureBakeResults();

            // check if obj2MeshCombinerMap matches our meshCombiner objects. If not, update it.
            Debug.Assert(obj2MeshCombinerMap != null);
            int mbcoCount = 0;
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                mbcoCount += meshCombiners[i].combinedMesh.GetNumObjectsInCombined();
            }
            if (obj2MeshCombinerMap.Count != mbcoCount)
            {
                obj2MeshCombinerMap.Clear();
                for (int combinerIdx = 0; combinerIdx < meshCombiners.Count; combinerIdx++)
                {
                    // get mesh combiner game objects
                    List<MB3_MeshCombinerSingle.MB_DynamicGameObject> dgos = meshCombiners[combinerIdx].combinedMesh.mbDynamicObjectsInCombinedMesh;
                    for (int dgoIdx = 0; dgoIdx < dgos.Count; dgoIdx++)
                    {
                        if (dgos[dgoIdx].gameObject != null)
                        {
                            int id = dgos[dgoIdx].gameObject.GetInstanceID();
                            dgos[dgoIdx].instanceID = id;
                        }

                        obj2MeshCombinerMap.Add(dgos[dgoIdx].instanceID, meshCombiners[combinerIdx]);
                    }
                }
            }
            Debug.Assert(obj2MeshCombinerMap.Count == mbcoCount);
            if (gos != null)
            {
                for (int i = 0; i < gos.Length; i++)
                {
                    if (gos[i] == null)
                    {
                        Debug.LogError("The " + i + "th object on the list of objects to combine is 'None'. Use Command-Delete on Mac OS X; Delete or Shift-Delete on Windows to remove this one element.");
                        return false;
                    }
                    if (_validationLevel >= MB2_ValidationLevel.robust)
                    {
                        for (int j = i + 1; j < gos.Length; j++)
                        {
                            if (gos[i] == gos[j])
                            {
                                Debug.LogError("GameObject " + gos[i] + "appears twice in list of game objects to add");
                                return false;
                            }
                        }
                        if (obj2MeshCombinerMap.ContainsKey(gos[i].GetInstanceID()))
                        {
                            bool isInDeleteList = false;
                            if (deleteGOinstanceIDs != null)
                            {
                                for (int k = 0; k < deleteGOinstanceIDs.Length; k++)
                                {
                                    if (deleteGOinstanceIDs[k] == gos[i].GetInstanceID()) isInDeleteList = true;
                                }
                            }
                            if (!isInDeleteList)
                            {
                                Debug.LogError("GameObject " + gos[i] + " is already in the combined mesh " + gos[i].GetInstanceID());
                                return false;
                            }
                        }
                    }
                }
            }
            if (deleteGOinstanceIDs != null)
            {
                if (_validationLevel >= MB2_ValidationLevel.robust)
                {
                    for (int i = 0; i < deleteGOinstanceIDs.Length; i++)
                    {
                        for (int j = i + 1; j < deleteGOinstanceIDs.Length; j++)
                        {
                            if (deleteGOinstanceIDs[i] == deleteGOinstanceIDs[j])
                            {
                                Debug.LogError("GameObject " + deleteGOinstanceIDs[i] + "appears twice in list of game objects to delete");
                                return false;
                            }
                        }
                        if (!obj2MeshCombinerMap.ContainsKey(deleteGOinstanceIDs[i]))
                        {
                            Debug.LogWarning("GameObject with instance ID " + deleteGOinstanceIDs[i] + " on the list of objects to delete is not in the combined mesh.");
                        }
                    }
                }
            }
            return true;
        }

        void _distributeAmongBakers(GameObject[] gos, int[] deleteGOinstanceIDs)
        {
            if (gos == null) gos = empty;
            if (deleteGOinstanceIDs == null) deleteGOinstanceIDs = emptyIDs;

            if (resultSceneObject == null) resultSceneObject = new GameObject("CombinedMesh-" + name);

            //PART 2 ==== calculate which bakers to add objects to
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                meshCombiners[i].extraSpace = _maxVertsInMesh - meshCombiners[i].combinedMesh.GetMesh().vertexCount;
            }
            //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1");

            //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.1");		
            //first delete game objects from the existing combinedMeshes keep track of free space
            for (int i = 0; i < deleteGOinstanceIDs.Length; i++)
            {
                CombinedMesh c = null;
                if (obj2MeshCombinerMap.TryGetValue(deleteGOinstanceIDs[i], out c))
                {
                    if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Removing " + deleteGOinstanceIDs[i] + " from meshCombiner " + meshCombiners.IndexOf(c));
                    MB3_MeshCombinerSingle.MB_DynamicGameObject dgo;
                    c.combinedMesh.InstanceID2DGO(deleteGOinstanceIDs[i], out dgo);
                    c.numVertsInListToDelete += dgo.numVerts;
                    c.gosToDelete.Add(deleteGOinstanceIDs[i]);
                }
                else
                {
                    Debug.LogWarning("Object " + deleteGOinstanceIDs[i] + " in the list of objects to delete is not in the combined mesh.");
                }
            }
            for (int i = 0; i < gos.Length; i++)
            {
                GameObject go = gos[i];
                int numVerts = MB_Utility.GetMesh(go).vertexCount;
                CombinedMesh cm = null;
                for (int j = 0; j < meshCombiners.Count; j++)
                {
                    if (meshCombiners[j].extraSpace + meshCombiners[j].numVertsInListToDelete - meshCombiners[j].numVertsInListToAdd > numVerts)
                    {
                        cm = meshCombiners[j];
                        if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Added " + gos[i] + " to combinedMesh " + j, LOG_LEVEL);
                        break;
                    }
                }
                if (cm == null)
                {
                    cm = new CombinedMesh(maxVertsInMesh, _resultSceneObject, _LOG_LEVEL);
                    _setMBValues(cm.combinedMesh);
                    meshCombiners.Add(cm);
                    if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Created new combinedMesh");
                }
                cm.gosToAdd.Add(go);
                cm.numVertsInListToAdd += numVerts;
                //			obj2MeshCombinerMap.Add(go,cm);
            }
        }

        bool _bakeStep1(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource)
        {
            //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.2");	
            //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); 
            //PART 3 ==== Add delete meshes from combined
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                CombinedMesh cm = meshCombiners[i];
                if (cm.combinedMesh.targetRenderer == null)
                {
                    cm.combinedMesh.resultSceneObject = _resultSceneObject;
                    cm.combinedMesh.BuildSceneMeshObject(gos, true);
                    if (_LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("BuildSO combiner {0} goID {1} targetRenID {2} meshID {3}", i, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID());

                }
                else
                {
                    if (resultSceneObject != null && cm.combinedMesh.targetRenderer.transform.parent != resultSceneObject.transform)
                    {
                        Debug.LogError("targetRender objects must be children of resultSceneObject");
                        return false;
                    }
                }
                if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0)
                {
                    cm.combinedMesh.AddDeleteGameObjectsByID(cm.gosToAdd.ToArray(), cm.gosToDelete.ToArray(), disableRendererInSource);
                    if (_LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Baked combiner {0} obsAdded {1} objsRemoved {2} goID {3} targetRenID {4} meshID {5}", i, cm.gosToAdd.Count, cm.gosToDelete.Count, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID());
                }
                Renderer r = cm.combinedMesh.targetRenderer;
                Mesh m = cm.combinedMesh.GetMesh();
                if (r is MeshRenderer)
                {
                    MeshFilter mf = r.gameObject.GetComponent<MeshFilter>();
                    mf.sharedMesh = m;
                }
                else
                {
                    SkinnedMeshRenderer smr = (SkinnedMeshRenderer)r;
                    smr.sharedMesh = m;
                }
            }
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                CombinedMesh cm = meshCombiners[i];
                for (int j = 0; j < cm.gosToDelete.Count; j++)
                {
                    obj2MeshCombinerMap.Remove(cm.gosToDelete[j]);
                }
            }
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                CombinedMesh cm = meshCombiners[i];
                for (int j = 0; j < cm.gosToAdd.Count; j++)
                {
                    obj2MeshCombinerMap.Add(cm.gosToAdd[j].GetInstanceID(), cm);
                }
                if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0)
                {
                    cm.gosToDelete.Clear();
                    cm.gosToAdd.Clear();
                    cm.numVertsInListToDelete = 0;
                    cm.numVertsInListToAdd = 0;
                    cm.isDirty = true;
                }
            }
            //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); 
            if (LOG_LEVEL >= MB2_LogLevel.debug)
            {
                string s = "Meshes in combined:";
                for (int i = 0; i < meshCombiners.Count; i++)
                {
                    s += " mesh" + i + "(" + meshCombiners[i].combinedMesh.GetObjectsInCombined().Count + ")\n";
                }
                s += "children in result: " + resultSceneObject.transform.childCount;
                MB2_Log.LogDebug(s, LOG_LEVEL);
            }
            if (meshCombiners.Count > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public override void ClearBuffers()
        {
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                meshCombiners[i].combinedMesh.ClearBuffers();
            }
            obj2MeshCombinerMap.Clear();
            _bakeStatus = MeshCombiningStatus.preAddDeleteOrUpdate;
        }

        public override void ClearMesh()
        {
            // For the MultiMeshCombiner we want to destroy because that is what an "empty" multi mesh combiner looks like.
            DestroyMesh();
            ClearBuffers();
        }

        public override void ClearMesh(MB2_EditorMethodsInterface editorMethods)
        {
            DestroyMeshEditor(editorMethods);
            ClearBuffers();
        }

        internal override void _DisposeRuntimeCreated()
        {
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                meshCombiners[i].combinedMesh._DisposeRuntimeCreated();
            }
        }

        public override void DestroyMesh()
        {
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                if (meshCombiners[i].combinedMesh.targetRenderer != null)
                {
                    MB_Utility.Destroy(meshCombiners[i].combinedMesh.targetRenderer.gameObject);
                }
                
                meshCombiners[i].combinedMesh.Dispose();
            }

            obj2MeshCombinerMap.Clear();
            meshCombiners.Clear();
        }

        public override void DestroyMeshEditor(MB2_EditorMethodsInterface editorMethods)
        {
            editorMethods.Destroy(resultSceneObject);
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                //if (meshCombiners[i].combinedMesh.targetRenderer != null)
                //{
                //    editorMethods.Destroy(meshCombiners[i].combinedMesh.targetRenderer.gameObject);
                //}

                meshCombiners[i].combinedMesh.ClearMesh();
            }

            obj2MeshCombinerMap.Clear();
            meshCombiners.Clear();
        }

        void _setMBValues(MB3_MeshCombinerSingle targ)
        {
            targ.validationLevel = _validationLevel;
            targ.textureBakeResults = textureBakeResults;
            
            // Even though the MultiMeshBaker supports baking into prefabs, the sub-combiners don't do bake into prefab when
            // this is happening. They do bake into sceneObject, then the MultiMeshBaker takes their output and combines it
            // into a prefab.
            targ.outputOption = MB2_OutputOptions.bakeIntoSceneObject;
            if (settingsHolder != null)
            {
                targ.settingsHolder = settingsHolder;
            } else 
            {
                targ.renderType = renderType;
                targ.lightmapOption = lightmapOption;
                targ.doNorm = doNorm;
                targ.doTan = doTan;
                targ.doCol = doCol;
                targ.doUV = doUV;
                targ.doUV3 = doUV3;
                targ.doUV4 = doUV4;
                targ.doUV5 = doUV5;
                targ.doUV6 = doUV6;
                targ.doUV7 = doUV7;
                targ.doUV8 = doUV8;
                targ.doBlendShapes = doBlendShapes;
                targ.optimizeAfterBake = optimizeAfterBake;
                targ.pivotLocationType = pivotLocationType;
                targ.uv2UnwrappingParamsHardAngle = uv2UnwrappingParamsHardAngle;
                targ.uv2UnwrappingParamsPackMargin = uv2UnwrappingParamsPackMargin;
                targ.assignToMeshCustomizer = assignToMeshCustomizer;
            }
        }

        public override List<Material> GetMaterialsOnTargetRenderer()
        {
            HashSet<Material> hs = new HashSet<Material>();
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                hs.UnionWith(meshCombiners[i].combinedMesh.GetMaterialsOnTargetRenderer());
            }
            List<Material> outMats = new List<Material>(hs);
            return outMats;
        }

        public override void CheckIntegrity()
        {
            if (!MB_Utility.DO_INTEGRITY_CHECKS) return;
            for (int i = 0; i < meshCombiners.Count; i++)
            {
                meshCombiners[i].combinedMesh.CheckIntegrity();
            }
        }
    }
}